import {
  AxiosRequestConfig,
  AxiosResponse,
  AxiosPromise,
  ResolvedFn,
  RejectedFn,
  Method
} from '../types'
import InterceptorManager from './InterceptorManager'
import dispatchRequest, { transformURL } from './dispatchRequest'
import mergeConfig from './mergeConfig'

const requestMethodWithoutData = Symbol('requestMethodWithoutData')
const requestMethodWithData = Symbol('requestMethodWithData')

interface Interceptors {
  request: InterceptorManager<AxiosRequestConfig>
  response: InterceptorManager<AxiosResponse>
}

interface PromiseChain<T> {
  resolved: ResolvedFn<T> | ((config: AxiosRequestConfig) => AxiosPromise)
  rejected?: RejectedFn
}

export default class Axios {
  defaults: AxiosRequestConfig // 为 Axios 类添加 defaults 属性
  interceptors: Interceptors

  constructor(initConfig: AxiosRequestConfig) {
    this.defaults = initConfig
    this.interceptors = {
      request: new InterceptorManager<AxiosRequestConfig>(),
      response: new InterceptorManager<AxiosResponse>()
    }
  }

  request(url: any, config?: any): AxiosPromise {
    if (typeof url === 'string') {
      if (!config) {
        config = {}
      }
      config.url = url
    } else {
      config = url
    }

    // 发送请求前，把默认配置 defaults 和传入的 config 做合并
    config = mergeConfig(this.defaults, config)

    // 定义链式调用的链，
    // 链的结构[...n 个请求 interceptor, { resolved: dispatchRequest, rejected: undefined }, ...n 个相应 interceptor]
    const chain: PromiseChain<any>[] = [
      {
        resolved: dispatchRequest,
        rejected: undefined
      }
    ]

    // 遍历请求拦截器，向 chain 中请求前添加各个请求拦截器
    this.interceptors.request.forEach(interceptor => {
      chain.unshift(interceptor)
    })

    // 遍历相应拦截器，向 chain 中请求后添加各个相应拦截器
    this.interceptors.response.forEach(interceptor => {
      chain.push(interceptor)
    })

    let promise = Promise.resolve(config)
    while (chain.length) {
      const { resolved, rejected } = chain.shift()!
      promise = promise.then(resolved, rejected)
    }
    return promise

    // return dispatchRequest(config)
  }

  get(url: string, config?: AxiosRequestConfig): AxiosPromise {
    return this[requestMethodWithoutData]('get', url, config)
  }

  delete(url: string, config?: AxiosRequestConfig): AxiosPromise {
    return this[requestMethodWithoutData]('delete', url, config)
  }

  head(url: string, config?: AxiosRequestConfig): AxiosPromise {
    return this[requestMethodWithoutData]('head', url, config)
  }

  options(url: string, config?: AxiosRequestConfig): AxiosPromise {
    return this[requestMethodWithoutData]('options', url, config)
  }

  post(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise {
    return this[requestMethodWithData]('post', url, data, config)
  }

  put(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise {
    return this[requestMethodWithData]('put', url, data, config)
  }

  patch(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise {
    return this[requestMethodWithData]('patch', url, data, config)
  }

  getUri(config?: AxiosRequestConfig): string {
    config = mergeConfig(this.defaults, config)
    return transformURL(config)
  }

  [requestMethodWithoutData](method: Method, url: string, config?: AxiosRequestConfig) {
    config = Object.assign(config || {}, { url, method })
    return this.request(config)
  }

  [requestMethodWithData](method: Method, url: string, data?: any, config?: AxiosRequestConfig) {
    config = Object.assign(config || {}, { url, method, data })
    return this.request(config)
  }
}
